﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace OpenReflect.Domain.Model.CodeStructure
{
	public class ExpressionVisitor
	{
		public virtual Expression Visit(Expression expression)
		{
			switch (expression.Type)
			{
				case ExpressionType.AssignExpression:
					return VisitAssign((AssignExpression) expression);
				case ExpressionType.BinaryExpression:
					return VisitBinary((BinaryExpression) expression);
				case ExpressionType.CallExpression:
					return VisitCall((CallExpression) expression);
				case ExpressionType.UnaryExpression:
					return VisitUnary((UnaryExpression) expression);
				case ExpressionType.CastExpression:
					return VisitCast((CastExpression) expression);
				case ExpressionType.ConstantExpression:
					return VisitConstant((ConstantExpression) expression);
				case ExpressionType.MemberAccessExpression:
					return VisitMemberAccess((MemberExpression) expression);
				default:
					throw new NotImplementedException();
			}
		}

		protected virtual Expression VisitUnary(UnaryExpression expression)
		{
			Expression exp = Visit(expression.Operand);
			if (exp != expression.Operand)
			{
				return new UnaryExpression(exp, expression.Operator);
			}
			return expression;
		}

		protected virtual Expression VisitBinary(BinaryExpression expression)
		{
			Expression left = Visit(expression.Left);
			Expression right = Visit(expression.Right);
			if (expression.Left != left || expression.Right != right)
				return new BinaryExpression(left, right, expression.Operator);
			return expression;
		}

		protected virtual Expression VisitCall(CallExpression expression)
		{
			Expression source = Visit(expression.Source);
			IEnumerable<Expression> arguments = VisitList(expression.Arguments);
			if (source != expression.Source || arguments != expression.Arguments)
				return new CallExpression(source, expression.Method, arguments);
			else
				return expression;
		}

		protected virtual Expression VisitCast(CastExpression expression)
		{
			Expression toBeCast = Visit(expression.Target);
			if (toBeCast != expression.Target)
				return new CastExpression(toBeCast, expression.ToType);
			else
				return expression;
		}

		protected virtual Expression VisitMemberAccess(MemberExpression expression)
		{
			Expression source = Visit(expression.Source);
			if (source != expression.Source)
				return new MemberExpression(source, expression.Member);
			else
				return expression;
		}

		protected virtual Expression VisitConstant(ConstantExpression expression)
		{
			return expression;
		}

		protected virtual Expression VisitAssign(AssignExpression expression)
		{
			Expression left = Visit(expression.Left);
			Expression right = Visit(expression.Right);
			if (left != expression.Left || right != expression.Right)
				return new AssignExpression(left, right);
			else
				return expression;
		}

		protected virtual ReadOnlyCollection<Expression> VisitList(ReadOnlyCollection<Expression> original)
		{
			List<Expression> list = null;
			for (int i = 0, n = original.Count; i < n; i++)
			{
				Expression p = Visit(original[i]);
				if (list != null)
					list.Add(p);
				else if (p != original[i])
				{
					list = new List<Expression>(n);
					for (int j = 0; j < i; j++)
						list.Add(original[j]);
					list.Add(p);
				}
			}

			if (list != null)
				return list.AsReadOnly();

			return original;
		}
	}
}